package org.dromara.business.config;

import io.netty.channel.ChannelOption;
import io.netty.handler.proxy.HttpProxyHandler;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.transport.ProxyProvider;
import reactor.util.retry.Retry;

import javax.net.ssl.SSLException;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

@Data
@Slf4j
@Configuration
@ConfigurationProperties(prefix = "webclient")
public class WebClientConfig {

    @Resource
    private TelegramBotProperties telegramBotProperties;

    // WebClient配置属性
    private Connection connection = new Connection();
    private Pool pool = new Pool();
    private Ssl ssl = new Ssl();
    private Retry retry = new Retry();

    @Data
    public static class Connection {
        // getters and setters
        private int connectTimeoutMs = 10000;
        private int readTimeoutSeconds = 30;
        private int writeTimeoutSeconds = 30;
        private int responseTimeoutSeconds = 30;
        private boolean tcpNoDelay = true;
        private boolean keepAlive = true;
    }

    @Data
    public static class Pool {
        private int maxConnections = 500;
        private int maxIdleTime = 20;
        private int maxLifeTime = 60;
        private int pendingAcquireMaxCount = 1000;
        private int pendingAcquireTimeout = 45;

    }
    @Data
    public static class Ssl {
        private boolean enabled = true;
        private boolean trustAll = false;
        private String[] protocols = {"TLSv1.2", "TLSv1.3"};

    }
    @Data
    public static class Retry {
        private int maxAttempts = 3;
        private long initialBackoffMs = 500;
        private long maxBackoffMs = 5000;
        private double backoffMultiplier = 2.0;

    }

    @Bean
    public ConnectionProvider connectionProvider() {
        return ConnectionProvider.builder("webclient-pool")
                .maxConnections(pool.getMaxConnections())
                .maxIdleTime(Duration.ofSeconds(pool.getMaxIdleTime()))
                .maxLifeTime(Duration.ofSeconds(pool.getMaxLifeTime()))
                .pendingAcquireMaxCount(pool.getPendingAcquireMaxCount())
                .pendingAcquireTimeout(Duration.ofSeconds(pool.getPendingAcquireTimeout()))
                .evictInBackground(Duration.ofSeconds(30))
                .build();
    }

    @Bean
    public HttpClient httpClient(ConnectionProvider connectionProvider) throws SSLException {
        HttpClient httpClient = HttpClient.create(connectionProvider)
                // 连接配置
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connection.getConnectTimeoutMs())
                .option(ChannelOption.TCP_NODELAY, connection.isTcpNoDelay())
                .option(ChannelOption.SO_KEEPALIVE, connection.isKeepAlive())
                // 响应超时
                .responseTimeout(Duration.ofSeconds(connection.getResponseTimeoutSeconds()))
                // 读写超时处理器
                .doOnConnected(conn -> conn
                        .addHandlerLast(new ReadTimeoutHandler(connection.getReadTimeoutSeconds(), TimeUnit.SECONDS))
                        .addHandlerLast(new WriteTimeoutHandler(connection.getWriteTimeoutSeconds(), TimeUnit.SECONDS))
                );

        // SSL配置
        if (ssl.isEnabled()) {
            httpClient = httpClient.secure(sslSpec -> {
                try {
                    SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();

                    if (ssl.isTrustAll()) {
                        log.warn("SSL Trust All is enabled - this should not be used in production!");
                        sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
                    }

                    if (ssl.getProtocols() != null && ssl.getProtocols().length > 0) {
                        sslContextBuilder.protocols(ssl.getProtocols());
                    }

                    sslSpec.sslContext(sslContextBuilder.build());
                } catch (SSLException e) {
                    log.error("Failed to configure SSL context", e);
                    throw new RuntimeException("SSL configuration failed", e);
                }
            });
        }

        // 代理配置
        TelegramBotProperties.Proxy proxyConfig = telegramBotProperties.getProxy();
        if (proxyConfig != null && proxyConfig.getEnabled()) {
            if (proxyConfig.getUsername() != null && !proxyConfig.getUsername().isEmpty()) {
                // 使用自定义代理处理器支持认证
                HttpProxyHandler proxyHandler = proxyConfig.getPassword() != null
                    ? new HttpProxyHandler(new InetSocketAddress(proxyConfig.getHost(), proxyConfig.getPort()),
                                          proxyConfig.getUsername(), proxyConfig.getPassword())
                    : new HttpProxyHandler(new InetSocketAddress(proxyConfig.getHost(), proxyConfig.getPort()));

                httpClient = httpClient.doOnConnected(connection ->
                    connection.addHandlerFirst(proxyHandler));

                log.info("WebClient configured with authenticated HTTP proxy: {}@{}:{}",
                        proxyConfig.getUsername(), proxyConfig.getHost(), proxyConfig.getPort());
            } else {
                // 使用标准代理配置
                ProxyProvider.Proxy proxyType = "SOCKS5".equalsIgnoreCase(proxyConfig.getType())
                        ? ProxyProvider.Proxy.SOCKS5
                        : ProxyProvider.Proxy.HTTP;

                httpClient = httpClient.proxy(proxy -> {
                    proxy.type(proxyType)
                         .socketAddress(new InetSocketAddress(proxyConfig.getHost(), proxyConfig.getPort()));
                });

                log.info("WebClient configured with {} proxy: {}:{}",
                        proxyType, proxyConfig.getHost(), proxyConfig.getPort());
            }
        }

        return httpClient;
    }

    @Bean
    public WebClient.Builder webClientBuilder(HttpClient httpClient) {
        return WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .defaultHeader("Content-Type", "application/json")
                .defaultHeader("User-Agent", "Cross-Chain-Bridge/1.0")
                .defaultHeader("Accept", "application/json")
                .filter(logRequest())
                .filter(logResponse())
                .filter(retryFilter())
                .filter(errorHandlingFilter());
    }

    private ExchangeFilterFunction logRequest() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            if (log.isDebugEnabled()) {
                log.debug("Request: {} {}", clientRequest.method(), clientRequest.url());
                clientRequest.headers().forEach((name, values) ->
                        values.forEach(value -> log.debug("Request Header: {}={}", name, value)));
            }
            return Mono.just(clientRequest);
        });
    }

    private ExchangeFilterFunction logResponse() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
            if (log.isDebugEnabled()) {
                log.debug("Response Status: {}", clientResponse.statusCode());
                clientResponse.headers().asHttpHeaders().forEach((name, values) ->
                        values.forEach(value -> log.debug("Response Header: {}={}", name, value)));
            }
            return Mono.just(clientResponse);
        });
    }

    private ExchangeFilterFunction retryFilter() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
            if (clientResponse.statusCode().is5xxServerError()) {
                return Mono.error(new RuntimeException("Server error: " + clientResponse.statusCode()));
            }
            return Mono.just(clientResponse);
        });
    }

    private ExchangeFilterFunction errorHandlingFilter() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
            if (clientResponse.statusCode().isError()) {
                return clientResponse.bodyToMono(String.class)
                        .flatMap(errorBody -> {
                            log.error("HTTP Error {}: {}", clientResponse.statusCode(), errorBody);
                            return Mono.error(new RuntimeException(
                                    String.format("HTTP %s: %s", clientResponse.statusCode(), errorBody)));
                        })
                        .then(Mono.just(clientResponse));
            }
            return Mono.just(clientResponse);
        });
    }

    @Bean
    public WebClient webClient(WebClient.Builder webClientBuilder) {
        return webClientBuilder
                .filter((request, next) ->
                        next.exchange(request)
                                .retryWhen(reactor.util.retry.Retry.backoff(
                                        retry.getMaxAttempts(),
                                        Duration.ofMillis(retry.getInitialBackoffMs()))
                                        .maxBackoff(Duration.ofMillis(retry.getMaxBackoffMs()))
                                        .multiplier(retry.getBackoffMultiplier())
                                        .filter(throwable -> {
                                            // 只对网络错误和5xx错误进行重试
                                            if (throwable instanceof java.io.IOException) {
                                                log.warn("WebClient retry for network error: {}", throwable.getMessage());
                                                return true;
                                            }
                                            if (throwable instanceof RuntimeException &&
                                                throwable.getMessage() != null &&
                                                throwable.getMessage().contains("Server error")) {
                                                log.warn("WebClient retry for server error: {}", throwable.getMessage());
                                                return true;
                                            }
                                            return false;
                                        })
                                        .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
                                            log.error("WebClient retry exhausted after {} attempts",
                                                    retrySignal.totalRetries());
                                            return retrySignal.failure();
                                        })
                                )
                )
                .build();
    }
}
